import { BehaviorSubject, Subject, Observable } from 'rxjs'
import { scan, map, publishReplay, refCount } from 'rxjs/operators'

import { Product } from './productModel'

const storageName = `test-products`
const storage = localStorage.getItem(storageName) || '[]'
let initialProducts: Product[]
try {
  initialProducts = JSON.parse(storage)
} catch (err) {
  initialProducts = []
  console.log(`恢复历史购物车数据失败： ${JSON.stringify(err)}`)
}

type Operation = (products: Product[]) => Product[]

class shopcarService {
  public product$: Observable<Product[]>
  /**
   * 操作序列
   */
  private update$ = new BehaviorSubject((products: Product[]) => products)
  /**
   * 添加一个商品
   * 如果商品id存在，则改为调用 increase method
   */
  private add$: Subject<Product> = new Subject()
  /**
   * 移除一个商品
   */
  private remove$: Subject<string> = new Subject()
  /**
   * 移除所有商品
   */
  private removeAll$: Subject<{}> = new Subject()
  /**
   * 提高指定商品的数量
   */
  private increase$: Subject<string> = new Subject()
  /**
   * 减少指定商品的数量
   */
  private reduce$: Subject<string> = new Subject()
  /**
   * 修改指定商品的数量
   */
  private modify$: Subject<{ id: string, type: string, value: string|number }> = new Subject()
  /**
   * 改变指定商品选中状态
   */
  private toggle$: Subject<string> = new Subject()
  /**
   * 改变所有商品选中状态
   */
  private toggleAll$: Subject<{}> = new Subject()

  private addProduct$: Subject<Product> = new Subject()
  private removeProduct$: Subject<string> = new Subject()
  private removeAllProduct$: Subject<{}> = new Subject()
  private increaseProduct$: Subject<string> = new Subject()
  private reduceProduct$: Subject<string> = new Subject()
  private modifyProduct$: Subject<{ id: string, type: string, value: string|number }> = new Subject()
  private toggleProduct$: Subject<string> = new Subject()
  private toggleAllProduct$: Subject<{}> = new Subject()

  constructor () {
    this.product$ = this.update$.pipe(
      scan((products: Product[], operation: Operation) => operation(products), initialProducts),
      publishReplay(1),
      refCount()
    )

    /**
     * 每次操作过后储存相应内容到localStorage
     */
    this.product$.forEach((products: Product[]) => localStorage.setItem(storageName, JSON.stringify(products)))

    this.add$.pipe(
      map((product: Product) => (products: Product[]) => {
        const { id } = product
        if (products.find(product => product.id === id)) {
          this.increase$.next(id)
          return products
        } else {
          return products.concat(product)
        }
      })
    ).subscribe(this.update$)

    this.remove$.pipe(
      map(id => (products: Product[]) => products.filter(products => products.id !== id))
    ).subscribe(this.update$)

    this.removeAll$.pipe(
      map(() => () => [])
    ).subscribe(this.update$)

    this.increase$.pipe(
      map((id: string) => (products: Product[]) => {
        const targetProduct = products.find(product => product.id === id)
        targetProduct && ++targetProduct.number
        return products
      })
    ).subscribe(this.update$)

    this.reduce$.pipe(
      map((id: string) => (products: Product[]) => {
        const targetProduct = products.find(product => product.id === id)
        targetProduct && --targetProduct.number
        return products
      })
    ).subscribe(this.update$)

    this.modify$.pipe(
      map((opt: { id: string, type: string, value: string|number }) => (products: Product[]) => {
        const { id, type, value } = opt
        const targetProduct = products.find(product => product.id === id)
        // es3 ready
        targetProduct && (type in targetProduct) && (targetProduct[type] = value)
        return products
      })
    ).subscribe(this.update$)

    this.toggle$.pipe(
      map((id: string) => (products: Product[]) => {
        const targetProduct = products.find(product => product.id === id)
        targetProduct && (targetProduct.toggle = !targetProduct.toggle)
        return products
      })
    ).subscribe(this.update$)

    this.toggleAll$.pipe(
      map(() => (products: Product[]) => {
        products.forEach(product => product.toggle = false)
        return products
      })
    ).subscribe(this.update$)

    this.addProduct$.subscribe(this.add$)
    this.removeProduct$.subscribe(this.remove$)
    this.removeAllProduct$.subscribe(this.removeAll$)
    this.increaseProduct$.subscribe(this.increase$)
    this.reduceProduct$.subscribe(this.reduce$)
    this.modifyProduct$.subscribe(this.modify$)
    this.toggleProduct$.subscribe(this.toggle$)
    this.toggleAllProduct$.subscribe(this.toggleAll$)
  }

  public add (product: Product) {
    this.addProduct$.next(new Product(product))
  }
  public remove (id: string) {
    this.removeProduct$.next(id)
  }
  public removeAll () {
    this.removeAllProduct$.next()
  }
  public increase (id: string) {
    this.increaseProduct$.next(id)
  }
  public reduce (id: string) {
    this.reduceProduct$.next(id)
  }
  public modify (id: string, type: string, value: string|number) {
    this.modifyProduct$.next({id, type, value})
  }
  public toggle (id: string) {
    this.toggleProduct$.next(id)
  }
  public toggleAll () {
    this.toggleAllProduct$.next()
  }
}

export const ShopcarService = new shopcarService()

export default ShopcarService
